######################################################################
# Time-stamp: <05/08/11 15:49:57 ostolop>
######################################################################
package EP::Common::General;

use strict;
use Exporter;

use POSIX;

use EP::Config;
use EP::Common;
use EP::Common::Error ':try';

# data types
use constant UNKNOWN => -1;
use constant NA      =>  0;
use constant NUMERIC => 1;

our @ISA = qw ( Exporter );
our @EXPORT = qw ( NUMERIC NA UNKNOWN system_call system_call_with_output );

######################################################################
# check whether the supplied session is ok
######################################################################
sub valid_session {
  my ($session_id) = @_;

  print "No session id found\n" and return 0 if not defined $session_id;

  #check whether the session has expired
  my $sth = $EP::Config::h->{DBH}->prepare( "select expire from sessions_metadata where session_id = ?" );
  my $rc = $sth->execute($session_id);
  print STDERR $DBI::errstr and return 0 if !$rc;

  my $expire = $sth->fetchrow_array();
  $sth->finish;

  print "Invalid session id\n" and return 0 if !$expire;

  my $cur_time = time();

  #session has expired
  print "Session has expired\n" and return 0 if $cur_time >= $expire;

  $expire = $cur_time + ( $EP::Config::h->{EXPIRE_SESSION} * 60 * 60 );

  #increase the expiration time
  my $rc_up = $EP::Config::h->{DBH}->do( "update sessions_metadata set expire = $expire where session_id = \'$session_id\'" );
  print STDERR $DBI::errstr and return 0 if !$rc_up;

  print "Session id is valid: $session_id\n";
  return 1;
}

######################################################################
# Clean user-submitted URL
######################################################################
sub clean_url_name {
  my( $url ) = @_ ;

  $url =~ s/^\s+|\s+$//g;
  $url =~ s/^/http:\/\// unless $url =~ /^http\:\/\// ;
  $url =~ s/\s/_/;    # No spaces in URL

  my $urltmp = $url ;
  if ( $urltmp =~ tr/-a-zA-Z0-9.,&=:~_\///cd ) {
    print "URL $url is not valid, became:$urltmp\n" ;
    return;
  }

  return $url ;
}

sub cmv {
  my ( $nr_rows, $is_num, $div, $rem, $matrix ) = @_;

  print STDERR "started for $div, $rem\n";

  foreach my $row ( 0 .. $nr_rows ) {
    next if $row % $div == $rem;
    map { $_ = "NA" if not /$is_num/ } @{$$matrix[$row]};
  }

  print STDERR "finished for $div, $rem\n";
}

######################################################################
# detects NA elements in a data row (by replacing them
# with NA, in this instance).
######################################################################
sub check_missing_values {
  my ($matrix) = @_;

#  use threads;
#  use threads::shared;

  my $nr_rows    = $#$matrix;
  my $nr_columns = $#{$$matrix[0]};

  my $is_num = qr /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/o;

  foreach my $row ( 0 .. $nr_rows ) {
    map { $_ = "NA" if not /$is_num/ } @{$$matrix[$row]};
  }

#  my $div = 2;
#  my @cmvthreads;

#  use Storable qw ( dclone );
#  my @smatrix : shared = @{dclone($matrix)};

#  foreach my $rem ( 0 .. $div - 1 ) {
#    print STDERR "Creating thread for $rem\n";
#    push @cmvthreads, threads->create ( sub {
#           foreach my $row ( 0 .. $nr_rows ) {
#             next if $row % $div == $rem;
#             map { $_ = "NA" if not /$is_num/ } @{$smatrix[$row]};
#           }
#           print STDERR "Finished sub for $rem\n";
#         } );
##    push @cmvthreads, threads->create ( \&cmv, $nr_rows, $is_num, $div, $_, @smatrix );
#  }

#  $_->join() foreach @cmvthreads;

#  $matrix = \@smatrix;

##  foreach my $row ( 0 .. $nr_rows ) {
#    map { $_ = "NA" if not /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/o } @{$$matrix[$row]};

#    foreach my $col ( 0 .. $nr_columns ) {
#      $_ = data_type ( $$matrix[$row][$col] );

#    SWITCH: {
# NA == $_
#   && do {
#     $$matrix[$row][$col] = "NA";
#     last SWITCH;
#   };

# UNKNOWN == $_
#   && do {
#     die "Invalid, unknown data encountered in row $row, value $$matrix[$row][$col]!";
#     last SWITCH;
#   };
#      }
#    }
#  }
  return;
}

######################################################################
# return what data type the supplied value is until now only for
# missing values (returns NA) and numerical values (returns NUMERIC)
######################################################################
sub data_type {
  my ($value) = @_;

  if ( $value =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/o ) {
    return NUMERIC;
# } elsif ( $value =~ /^NA$|^\#N\/A$|^\s*$/o ) {
#   return NA;
  } else {
    return NA;
  }

  return UNKNOWN;
}

######################################################################
# calculate average of an array (can pass references or direct), ignoring NA's
######################################################################
sub na_mean {
  return mean ( grep { $_ ne "NA" } @{$_[0]} ) if ref($_[0]) eq "ARRAY";
  return mean ( grep { $_ ne "NA" } @_ );
}

######################################################################
# calculates mean of an array (can pass references or direct)
######################################################################
sub mean {
  my $cnt = scalar @{$_[0]}  if ref($_[0]) eq "ARRAY";
     $cnt = scalar @_;

  return sum ( @_ ) / $cnt;
}

######################################################################
# calculates sum of an array (can pass references or direct)
######################################################################
sub sum {
  my $sum = 0;

  if ( ref ($_[0]) eq "ARRAY" ) {
    $sum += $_ foreach @{$_[0]};
  } else {
    $sum += $_ foreach @_;
  }

  return $sum;
}

######################################################################
# general routine to calculate a log N of a number
######################################################################
sub calculate_logN {
  my ($nr, $log) = @_;

  return DBL_MIN if $nr == 0;
  return ( log10($nr) / log10($log) );
}

sub submit_jobs_and_wait {
  my ($jobs) = @_;
  my $rc;

  $_ = $EP::Config::h->{JOB_QUEUE};

 SWITCH: {
    /UNIX/
      && do {
  $rc = submit_jobs_unix ( $jobs );
  last SWITCH;
      };
    /PBS/
      && do {
  last SWITCH;
      };
    /LSF/
      && do {
  last SWITCH;
      };
  }

  return $rc;
}

sub submit_jobs_unix {
  my ($jobs) = @_;

  foreach my $job ( keys %$jobs ) {
    next if $jobs->{$job}{program} =~ /^\s*$/;

    my $cmd;

    $cmd = $jobs->{$job}{program};
    $cmd .= " " . $jobs->{$job}{param} if $jobs->{$job}{param};

    # redirection of output
    if ( $jobs->{$job}{stdout} and
   "$jobs->{$job}{stdout}" eq "$jobs->{$job}{stderr}"
       ) {
      $cmd .= " 1> $jobs->{$job}{stdout} 2>&1 ";
    } elsif ( $jobs->{$job}{stdout} ) {
      $cmd .= " >$jobs->{$job}{stdout}";
    } elsif ( $jobs->{$job}{stderr} ) {
      $cmd .= " 2>$jobs->{$job}{stderr}";
    }

    # timing
    $cmd = "$EP::Config::h->{TIME_CMD} $cmd";
    warn "[JOB] $cmd" if DEBUG;

    return if ( system_call ( $cmd ) ) != 1;
  }

  return 1;
}

######################################################################
# run program on command line and check the exit status
######################################################################
sub system_call {
  my ($cmd) = @_;

  my $rc = system $cmd;

  if ( $rc == 0 ) {
    warn "$cmd ran with normal status";
    return 1;
  }

  if ($? == -1) {
    warn"failed to execute $cmd: $!";
  }
  elsif ($? & 127) {
    printf STDERR "child ($cmd) died with signal %d, %s coredump\n",
     ($? & 127),  ($? & 128) ? 'with' : 'without';
  }
  else {
    printf STDERR "child ($cmd) exited with value %d $!\n", $? >> 8;
  }

  return -1;

#  if ($rc == 0) {
#    print STDERR "[EP_Core] $cmd ran with normal exit status\n"
#      if DEBUG == 1;
#  } elsif ($rc == 0xff00) {
#    print STDERR "[EP_Core] $cmd command failed: $!\n";
#  } elsif (($rc & 0xff) == 0) {
#    $rc >>= 8;
#    print STDERR "[EP_Core] $cmd ran with non-zero exit status $rc\n";
#  } else {
#    print STDERR "[EP_Core] $cmd ran with ";
#    if ($rc &   0x80) {
#      $rc &= ~0x80;
#      print STDERR "coredump from ";
#    }
#    print STDERR "signal $rc\n";
#  }
#
#  return ($rc == 0);
}

sub system_call_with_output {
  my $cmd = shift;

  my $out = `$cmd`;
  # TO DO (investigate)
  # my $rc  = $? >> 8;
  my $rc = 0;

  warn "Ran $cmd with exit code $rc" if DEBUG > 1;
  throw EP::Common::Error ( -value => SYS_CALL_WITH_NONZERO_STATUS ) if $rc != 0;
  return $out;
}

sub min {
  my $arr_ref = shift;

  warn "General::min requires an array reference!" and
    return undef if not ref($arr_ref) eq "ARRAY";

  my $min = $$arr_ref[0];
  foreach ( @$arr_ref ) {
    $min = $_ if $_ < $min;
  }

  return $min;
}

sub max {
  my $arr_ref = shift;

  warn "General::max requires an array reference!" and
    return undef if not ref($arr_ref) eq "ARRAY";

  my $max = $$arr_ref[0];
  foreach ( @$arr_ref ) {
    $max = $_ if $_ > $max;
  }

  return $max;
}

1;
